home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / RELALG.PY < prev    next >
Encoding:
Python Source  |  1999-07-28  |  12.5 KB  |  528 lines

  1.  
  2. """Simple relational algebra interpreter.
  3.  
  4. usage:
  5.  
  6.    To make the grammar
  7.  
  8.        python relalg.py make
  9.  
  10.    To run some relatoinal algebra expressions
  11.  
  12.        python relalg.py < expressions_file
  13.  
  14. """
  15.  
  16. # EDIT INSTALLDIR TO BE ABLE TO LOAD UNDER ANY CWD
  17. INSTALLDIR = "."
  18.  
  19. ## simple relational algebra using only the equality predicate
  20. ## note: string values cannot contain ;
  21.  
  22. ## statement sequencing using ; handled at higher level
  23.  
  24. relalg_rules = """
  25.  
  26. statement ::
  27.  
  28. @R statementassn :: statement >> assignment
  29.  
  30. @R statementexpr :: statement >> rexpr
  31.  
  32. @R assignment1 :: assignment >> name = rexpr
  33.  
  34. @R assignmentn :: assignment >> name = assignment
  35.  
  36. @R union :: rexpr >> rexpr U rterm
  37.  
  38. @R rterm :: rexpr >> rterm
  39.  
  40. @R minus :: rexpr >> rexpr - rterm
  41.  
  42. @R intersect :: rterm >> rterm intersect rfactor
  43.  
  44. @R join :: rterm >> rterm join rfactor
  45.  
  46. @R rfactor :: rterm >> rfactor
  47.  
  48. @R projection :: rfactor >> projection [ names ] rfactor
  49.  
  50. @R names0 :: names >>
  51.  
  52. @R namesn :: names >> names1
  53.  
  54. @R names11 :: names1 >> name
  55.  
  56. @R names1n :: names1 >> names1 name
  57.  
  58. @R selection :: rfactor >> selection ( condition ) rfactor
  59.  
  60. @R conditionor :: condition >> condition | boolfactor
  61.  
  62. @R condfactor :: condition >> boolfactor
  63.  
  64. @R factorand :: boolfactor >> boolfactor & boolprimary
  65.  
  66. @R factorprime :: boolfactor >> boolprimary
  67.  
  68. @R notprimary :: boolprimary >> ~ boolprimary
  69.  
  70. @R primarycondition :: boolprimary >> ( condition )
  71.  
  72. @R primaryeq :: boolprimary >> expression = expression
  73.  
  74. @R expname :: expression >> name
  75.  
  76. @R expvalue :: expression >> value
  77.  
  78. @R rename :: rfactor >> rename [ names ] to [ names ] rfactor
  79.  
  80. @R named :: rfactor >> name
  81.  
  82. @R factorexpr :: rfactor >> ( rexpr )
  83.  
  84. @R relationval :: rfactor >> [ names ] ( rows )
  85.  
  86. @R rows0 :: rows >>
  87.  
  88. @R rowsn :: rows >> somerows
  89.  
  90. @R somerows1 :: somerows >> row
  91.  
  92. @R somerowsn :: somerows >> somerows , row
  93.  
  94. @R emptyrow :: row >> NIL
  95.  
  96. @R row1 :: row >> value
  97.  
  98. @R rown :: row >> row value
  99.  
  100. @R valuenum :: value >> number
  101.  
  102. @R valuestr :: value >> string
  103. """
  104.  
  105. keywords = """
  106.   selection intersect rename projection to NIL U join
  107. """
  108.  
  109. puncts = """=^~|,-[]()&"""
  110.  
  111. nonterms = """
  112. statement assignment rexpr rterm value rfactor
  113. names names1 condition boolfactor boolprimary
  114. expression rows somerows row
  115. """
  116.  
  117. try:
  118.     from kjbuckets import *
  119. except ImportError:
  120.     from kjbuckets0 import *
  121.  
  122.  
  123. class relation:
  124.  
  125.    def __init__(self, names, rows):
  126.        #print "relation init", names, rows
  127.        names = self.names = tuple(names)
  128.        nameset = self.nameset = kjSet(names)
  129.        for r in rows:
  130.            if nameset != kjSet(r.keys()):
  131.                raise ValueError, \
  132.                 "bad names: "+`(names, r.items())`
  133.        self.rows = kjSet(rows)
  134.  
  135.    def __repr__(self):
  136.        from string import join
  137.        names = self.names
  138.        rows = self.rows.items()
  139.        if not rows:
  140.           nns = join(names)
  141.           replist = [nns, "="*len(nns), " --<empty>--"]
  142.           return join(replist, "\n")
  143.        #print names, rows
  144.        nnames = len(names)
  145.        if nnames==1:
  146.            replist = [names[0]]
  147.        else:
  148.            replist = [names]
  149.        for r in rows:
  150.            elt = r.dump(names)
  151.            replist.append(r.dump(names))
  152.        #print replist
  153.        if nnames==1:
  154.            replist = maxrep(replist)
  155.        else:
  156.            transpose = apply(map, tuple([None] + replist))
  157.            adjusted = map(maxrep, transpose)
  158.            replist = apply(map, tuple([None] + adjusted))
  159.            replist = map(join, replist)
  160.        replist.insert(1, "=" * len(replist[0]))
  161.        #print replist
  162.        return join(replist, "\n")
  163.        
  164. def maxrep(list):
  165.     list = map(str, list)
  166.     maxlen = max( map(len, list) )
  167.     for i in range(len(list)):
  168.         item = list[i]
  169.         litem = len(item)
  170.         list[i] = item + (" " * (maxlen-litem))
  171.     return list
  172.  
  173. # context is a simple dictionary of named relations
  174.  
  175. def elt0(l, c):
  176.     return l[0]
  177.  
  178. statementassn = elt0
  179.  
  180. def statementexpr(l, c):
  181.     from string import split, join
  182.     print
  183.     print "  --- expression result ---"
  184.     print
  185.     data = str(l[0])
  186.     print "   "+ join(split(data, "\n"), "\n   ")
  187.  
  188. def assignment1(l, c):
  189.     [name, eq, val] = l
  190.     c[name] = val
  191.     return val
  192.  
  193. assignmentn = assignment1
  194.  
  195. def check_compat(v1, v2):
  196.     names1, names2 = v1.names, v2.names
  197.     if names1 != names2:
  198.         raise ValueError, \
  199.         "operands not union compatible "+`(names1, names2)`
  200.     return names1, v1.rows, v2.rows
  201.  
  202. def union(l, c):
  203.     [v1, U, v2] = l
  204.     names1, r1, r2 = check_compat(v1, v2)
  205.     return relation(names1, (r1+r2).items())
  206.  
  207. rterm = elt0
  208.  
  209. def minus(l, c):
  210.     [v1, m, v2] = l
  211.     names1, r1, r2 = check_compat(v1, v2)
  212.     return relation(names1, (r1-r2).items())
  213.  
  214. def intersect(l, c):
  215.     [v1, i, v2] = l
  216.     names1, r1, r2 = check_compat(v1, v2)
  217.     return relation(names1, (r1&r2).items())
  218.  
  219. def join(l, c):
  220.     [v1, j, v2] = l
  221.     n1, n2 = v1.names, v2.names
  222.     r1, r2 = v1.rows.items(), v2.rows.items()
  223.     n1s, n2s = kjSet(n1), kjSet(n2)
  224.     common = tuple((n1s&n2s).items())
  225.     result = kjSet()
  226.     if common:
  227.         # simple hashjoin
  228.         G = kjGraph()
  229.         for a in r1:
  230.             G[a.dump(common)] = a
  231.         for b in r2:
  232.             for a in G.neighbors(b.dump(common)):
  233.                 result[a+b] = 1
  234.     else:
  235.         for a in r1:
  236.             for b in r2:
  237.                 result[a+b] = 1
  238.     return relation( (n1s+n2s).items(), result.items() )
  239.  
  240. rfactor = elt0
  241.  
  242. def projection(l, c):
  243.     [p, b1, names, b2, val] = l
  244.     proj = kjSet(names)
  245.     result = kjSet()
  246.     for row in val.rows.items():
  247.         result[ proj * row ] = 1
  248.     return relation( names, result.items())
  249.  
  250. def emptylist(l, c):
  251.     return []
  252.  
  253. names0 = emptylist
  254.  
  255. namesn = elt0
  256.  
  257. def names11(l, c):
  258.     return l
  259.  
  260. def names1n(l, c):
  261.     [ns, n] = l
  262.     ns.append(n)
  263.     return ns
  264.  
  265. def selection(l, c):
  266.     [sel, p1, cond, p2, val] = l
  267.     return cond.filter(val)
  268.  
  269. ## conditions are not optimized at all!
  270.  
  271. class conditionor:
  272.     def __init__(self, l, c):
  273.         [self.c1, op, self.c2] = l
  274.     def filter(self, val):
  275.         v1 = self.c1.filter(val)
  276.         v2 = self.c2.filter(val)
  277.         return relation(v1.names, (v1.rows+v2.rows).items())
  278.  
  279. condfactor = elt0
  280.  
  281. class factorand(conditionor):
  282.     def filter(self, val):
  283.         v1 = self.c1.filter(val)
  284.         v2 = self.c2.filter(val)
  285.         return relation(v1.names, (v1.rows&v2.rows).items())
  286.  
  287. factorprime = elt0
  288.  
  289. class notprimary:
  290.     def __init__(self, l, c):
  291.         [n, self.c1] = l
  292.     def filter(self, val):
  293.         v1 = self.c1.filter(val)
  294.         return relation(v1.names, (val.rows-v1.rows).items())
  295.  
  296. def elt1(l, c):
  297.     return l[1]
  298.  
  299. primarycondition = elt1
  300.  
  301. class primaryeq:
  302.     def __init__(self, l, c):
  303.         [self.e1, eq, self.e2] = l
  304.     def filter(self, val):
  305.         rows = val.rows.items()
  306.         e1v = self.e1.value(rows)
  307.         e2v = self.e2.value(rows)
  308.         result = kjSet()
  309.         for (r, v1, v2) in map(None, rows, e1v, e2v):
  310.             if v1==v2:
  311.                 result[r] = 1
  312.         return relation(val.names, result.items())
  313.  
  314. class expname:
  315.     def __init__(self, l, c):
  316.         self.name = l[0]
  317.     def value(self, rows):
  318.         name = self.name
  319.         r = list(rows)
  320.         for i in xrange(len(r)):
  321.             r[i] = r[i][name]
  322.         return r
  323.  
  324. class expvalue(expname):
  325.     def value(self, rows):
  326.         return [self.name] * len(rows)
  327.  
  328. def rename(l, c):
  329.     [ren, b1, names, b2, to, b3, names2, b4, val] = l
  330.     if len(names)!=len(names2):
  331.         raise ValueError, "names lengths must match"+`(names1, names2)`
  332.     remap = kjDict(map(None, names2, names))
  333.     oldnames = kjSet(val.names)
  334.     addnames = kjSet(names2)
  335.     remnames = kjSet(names)
  336.     keepnames = oldnames - remnames
  337.     remap = remap + keepnames
  338.     if not remnames.subset(oldnames):
  339.         #print remnames, oldnames
  340.         raise ValueError, "old names not present"+`(names, val.names)`
  341.     newnames = keepnames+addnames
  342.     rows = val.rows.items()
  343.     for i in range(len(rows)):
  344.         rows[i] = remap*rows[i]
  345.     return relation(newnames.items(), rows)
  346.  
  347. def named(l, c):
  348.     [name] = l
  349.     return c[name]
  350.  
  351. def relationval(l, c):
  352.     [b1, names, b2, p1, rows, p2] = l
  353.     names = tuple(names)
  354.     ln = len(names)
  355.     for i in xrange(len(rows)):
  356.         this = rows[i]
  357.         lt = len(this)
  358.         if lt!=ln:
  359.            raise ValueError, "names, vals don't match"+`(names,this)`
  360.         if len(this)==1:
  361.            this = this[0]
  362.         else:
  363.            this = tuple(this)
  364.         rows[i] = kjUndump(names, this)
  365.     return relation(names, rows)
  366.  
  367. rows0 = emptylist
  368.  
  369. rowsn = elt0
  370.  
  371. def somerows1(l, c):
  372.     #print "somerows1", l
  373.     return l
  374.  
  375. def somerowsn(l, c):
  376.     #print "somerowsn", l
  377.     [sr, c, r] = l
  378.     sr.append(r)
  379.     return sr
  380.  
  381. emptyrow = emptylist
  382.  
  383. row1 = somerows1
  384.  
  385. def factorexpr(l, c):
  386.     return l[1]
  387.  
  388. def rown(l, c):
  389.     #print "rows", l
  390.     [r, v] = l
  391.     r.append(v)
  392.     return r
  393.  
  394. valuenum = valuestr = elt0
  395.  
  396. ## snarfed from sqlbind
  397. # note: all reduction function defs must precede this assign
  398. VARS = vars()
  399.  
  400. class punter:
  401.    def __init__(self, name):
  402.        self.name = name
  403.    def __call__(self, list, context):
  404.        print "punt:", self.name, list
  405.        return list
  406.        
  407. class tracer:
  408.    def __init__(self, name, fn):
  409.        self.name = name
  410.        self.fn = fn
  411.      
  412.    def __call__(self, list, context):
  413.        print "tracing", self.name, list
  414.        test = self.fn(list, context)
  415.        print self.name, "returns", test
  416.        return test
  417.  
  418. def BindRules(sqlg):
  419.     for name in sqlg.RuleNameToIndex.keys():
  420.         if VARS.has_key(name):
  421.            #print "binding", name
  422.            sqlg.Bind(name, VARS[name]) # nondebug
  423.            #sqlg.Bind(name, tracer(name, VARS[name]) ) # debug
  424.         else:
  425.            print "unbound", name
  426.            sqlg.Bind(name, punter(name))
  427.     return sqlg
  428.  
  429. ## snarfed from sqlgen
  430.  
  431. MARSHALFILE = "relalg.mar"
  432.  
  433. import string
  434. alphanum = string.letters+string.digits + "_"
  435. userdefre = "[%s][%s]*" % (string.letters +"_", alphanum)
  436. RACOMMENTREGEX = "COMMENT .*"
  437.  
  438. def userdeffn(str):
  439.     return str
  440.     
  441. charstre = "'[^\n']*'"
  442.  
  443. def charstfn(str):
  444.     return str[1:-1]
  445.     
  446. numlitre = "[%s][%s\.]*" % (string.digits, alphanum) # not really...
  447.  
  448. def numlitfn(str):
  449.     """Note: this is "safe" because regex
  450.        filters out dangerous things."""
  451.     return eval(str)
  452.  
  453. def DeclareTerminals(Grammar):
  454.     Grammar.Addterm("name", userdefre, userdeffn)
  455.     Grammar.Addterm("string", charstre, charstfn)
  456.     Grammar.Addterm("number", numlitre, numlitfn)
  457.     
  458. def Buildrelalg(filename=MARSHALFILE):
  459.     import kjParseBuild
  460.     SQLG = kjParseBuild.NullCGrammar()
  461.     #SQLG.SetCaseSensitivity(0)
  462.     DeclareTerminals(SQLG)
  463.     SQLG.Keywords(keywords)
  464.     SQLG.punct(puncts)
  465.     SQLG.Nonterms(nonterms)
  466.     # should add comments
  467.     SQLG.comments([RACOMMENTREGEX])
  468.     SQLG.Declarerules(relalg_rules)
  469.     print "working..."
  470.     SQLG.Compile()
  471.     filename = INSTALLDIR+"/"+filename
  472.     print "dumping to", filename
  473.     outfile = open(filename, "wb")
  474.     SQLG.MarshalDump(outfile)
  475.     outfile.close()
  476.     return SQLG
  477.     
  478. def reloadrelalg(filename=MARSHALFILE):
  479.     import kjParser
  480.     filename = INSTALLDIR+"/"+filename
  481.     infile = open(filename, "rb")
  482.     SQLG = kjParser.UnMarshalGram(infile)
  483.     infile.close()
  484.     DeclareTerminals(SQLG)
  485.     BindRules(SQLG)
  486.     return SQLG
  487.     
  488. def runfile(f):
  489.     from string import split, join
  490.     ragram = reloadrelalg()
  491.     context = {}
  492.     #f = open(filename, "r")
  493.     data = f.read()
  494.     #f.close()
  495.     from string import split, strip
  496.     commands = split(data, ";")
  497.     for c in commands:
  498.         if not strip(c): continue
  499.         print " COMMAND:"
  500.         data = str(c)
  501.         pdata = "  "+join(split(c, "\n"), "\n  ")
  502.         print pdata
  503.         test = ragram.DoParse1(c, context)
  504.         print
  505.  
  506. # c:\python\python relalg.py ratest.txt
  507.  
  508. if __name__=="__main__":
  509.     try:
  510.         done = 0
  511.         import sys
  512.         argv = sys.argv
  513.         if len(argv)>1:
  514.             command = argv[1]
  515.             if command=="make":
  516.                 print "building relational algebra grammar"
  517.                 Buildrelalg()
  518.                 done = 1
  519.         else:
  520.             runfile(sys.stdin)
  521.             done = 1
  522.     finally:
  523.         if not done:
  524.             print __doc__
  525.  
  526.  
  527.  
  528.